/*
 * DDR driver for inno DDR PHY.
 * Used by x1xxx
 *
 * Copyright (C) 2017 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <asm/io.h>
#include <asm/arch/x1830.h>
#include <asm/arch/x1830_sdram.h>
#include <ddr/ddr_chips.h>

struct ddr_params *ddr_params_p = NULL;

static void reset_controller(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0xf << 20, ddrc_regs + DDRC_CTRL);
	udelay(5000);
	writel(0x8 << 20, ddrc_regs + DDRC_CTRL);
	udelay(5000);
}

static void ddrc_post_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0x00910003, ddrc_regs + DDRC_REFCNT);
	writel(0x0000d91a, ddrc_regs + DDRC_CTRL);
}

static void ddrc_prev_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0x050f0a06, ddrc_regs + DDRC_TIMING(1));
	writel(0x021c0a07, ddrc_regs + DDRC_TIMING(2));
	writel(0x200a0722, ddrc_regs + DDRC_TIMING(3));
	writel(0x26640031, ddrc_regs + DDRC_TIMING(4));
	writel(0xff060405, ddrc_regs + DDRC_TIMING(5));
	writel(0x321c0505, ddrc_regs + DDRC_TIMING(6));

	/* DDRC memory map configure*/
	writel(0x000020f8, ddrc_regs + DDRC_MMAP0);
	writel(0x00002800, ddrc_regs + DDRC_MMAP1);
	writel(0x0000d91a & 0xffff8fff, ddrc_regs + DDRC_CTRL);
}

void ddr_inno_phy_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	/*
	 * ddr phy pll initialization
	 */
	writel(0x14, ddrc_regs + DDRP_PLL_FBDIV);
	writel(0x5, ddrc_regs + DDRP_PLL_PDIV);
	writel(0x1a, ddrc_regs + DDRP_PLL_CTRL);
	writel(0x18, ddrc_regs + DDRP_PLL_CTRL);
	while(!(readl(ddrc_regs + DDRC_PHY_INIT) & (1<<2))); //polling pll lock

	/*
	 * ddr phy register cfg
	 */
	writel(0x3, ddrc_regs + DDRP_DQ_WIDTH);

	writel(0x11, ddrc_regs + DDRP_MEM_CFG);  // MEMSEL  =  DDR2  ,    BURSEL = burst8
	writel(0x0d, ddrc_regs + DDRP_CHANNEL_EN);
	writel(((0x00000e73 & 0xf0) >> 4) - 1, ddrc_regs + DDRP_CWL);
	writel((0x00000e73 & 0xf0) >> 4, ddrc_regs + DDRP_CL);

	writel(0x0, ddrc_regs + DDRP_AL);
}

void ddrc_dfi_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(1, ddrc_regs + DDRC_PHY_INIT); //start high
	writel(0, ddrc_regs + DDRC_PHY_INIT); //start low
	while(!(readl(ddrc_regs + DDRC_PHY_INIT) & (1 << 1))); //polling dfi init comp

	writel(0, ddrc_regs + DDRC_CTRL);
	writel(0x0ae88a42, ddrc_regs + DDRC_CFG);
	writel(0x2, ddrc_regs + DDRC_CTRL);

	/*DDR2*/
	writel(0x211, ddrc_regs + DDRC_LMR);
	writel(0, ddrc_regs + DDRC_LMR);

	writel(0x311, ddrc_regs + DDRC_LMR);
	writel(0, ddrc_regs + DDRC_LMR);

	writel(0x111, ddrc_regs + DDRC_LMR);
	writel(0, ddrc_regs + DDRC_LMR);

	writel(((0x00000e73) << 12) | 0x011, ddrc_regs + DDRC_LMR);
	writel(0, ddrc_regs + DDRC_LMR);
}

void ddrp_wl_training(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0x51, ddrc_regs + DDRP_MEM_CFG);

	writel(0x24, (void __iomem *)0xb3011028);
}

/*
 * Name     : phy_calibration()
 * Function : control the RX DQS window delay to the DQS
 * */
void phy_calibration(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	int m = readl(ddrc_regs + DDRP_TRAINING_CTRL);

	m = 0xa1;
	writel(m, ddrc_regs + DDRP_TRAINING_CTRL);
	while (0x3 != readl(ddrc_regs + DDRP_CALIB_DONE));

	writel(0xa0, ddrc_regs + DDRP_TRAINING_CTRL);
}

/* DDR sdram init */
void sdram_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	void __iomem *cpm_regs = (void __iomem *)CPM_BASE;
	uint32_t ddr_cdr;

	/* setup DDR clock */
	ddr_cdr = CONFIG_SYS_MPLL_FREQ / CONFIG_SYS_MEM_FREQ - 1;
	writel(ddr_cdr | CPM_DDRCDR_DCS_MPLL | CPM_DDRCDR_CE, cpm_regs + CPM_DDRCDR);
	while (readl(cpm_regs + CPM_DDRCDR) & CPM_DDRCDR_DDR_BUSY);

	/*
	 * WARNING: 2015-01-08
	 * DDR CLK GATE(CPM_DRCG 0xB00000D0), BIT6 must set to 1 (or 0x40).
	 * If clear BIT6, chip memory will not stable, gpu hang occur.
	 */
	writel(0x73 | (1 << 6), cpm_regs + CPM_DRCG);
	udelay(1000);
	writel(0x71 | (1 << 6), cpm_regs + CPM_DRCG);
	udelay(1000);

	reset_controller();

	ddr_inno_phy_init();
	ddrc_dfi_init();

	ddrp_wl_training();
        /* DDR Controller init*/
	ddrc_prev_init();
	phy_calibration();
	ddrc_post_init();

	writel(readl(ddrc_regs + DDRC_STATUS) & ~DDRC_STATUS_MISS, ddrc_regs + DDRC_STATUS);

	writel(0 , ddrc_regs + DDRC_DLP);
}

DECLARE_GLOBAL_DATA_PTR;

int dram_init(void)
{
#ifndef EMC_LOW_SDRAM_SPACE_SIZE
#define EMC_LOW_SDRAM_SPACE_SIZE 0x10000000 /* 256M */
#endif /* EMC_LOW_SDRAM_SPACE_SIZE */
	unsigned int ram_size;

	ram_size = (unsigned int)(134217728);
	if (ram_size > EMC_LOW_SDRAM_SPACE_SIZE)
		ram_size = EMC_LOW_SDRAM_SPACE_SIZE;

	gd->ram_size = ram_size;

	return 0;
}
